﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using LinqExpression = System.Linq.Expressions.Expression;


namespace SimpleData.Infrastructure.JavaScript
{
	/// <summary>
	/// Representa um contexto de execução do código javascript
	/// </summary>
	public sealed class JavaScriptCodeContext
	{
		internal JavaScriptCodeContext()
			: this( "root", null )
		{ }

		public JavaScriptCodeContext( string name, JavaScriptCodeContext parent )
		{
			this.Parent = parent;
			this.Statements = new List<LinqExpression>();
			this.Name = name;

			if ( parent != null )
				this.Locals = new SymbolTable( parent.Locals );
			else
				this.Locals = new SymbolTable();

		}

		/// <summary>
		/// Obtém a lista de variáveis globais do contexto
		/// </summary>
		public SymbolTable Globals
		{
			get
			{
				return this.Locals.Parent;
			}
		}

		/// <summary>
		/// Obtém a tabela com as variáveis locais
		/// </summary>
		public SymbolTable Locals
		{ get; private set; }

		/// <summary>
		/// Obtém ou define o nome do contexto.
		/// </summary>
		public string Name
		{ get; private set; }

		/// <summary>
		/// Obtém o contexto acima deste contexto
		/// </summary>
		public JavaScriptCodeContext Parent
		{ get; private set; }

		/// <summary>
		/// Obtém a lista de comandos que foram dados dentro deste contexto
		/// </summary>
		public List<LinqExpression> Statements
		{ get; private set; }

		/// <summary>
		/// Obtém o bloco de expressões
		/// </summary>
		/// <returns></returns>
		public LinqExpression GetBlock()
		{
			// Se possui variáveis
			if ( this.Locals.GetVariables().Any() )
				return LinqExpression.Block( this.Locals.GetVariables(), this.Statements );
			else if ( this.Statements.Count == 1 )
				return this.Statements[ 0 ];
			else // if ( this.Statements.Count > 1 )
				return LinqExpression.Block( this.Statements );
		}

		public void PushStatement( LinqExpression statement )
		{
			this.Statements.Add( statement );
		}

		/// <summary>
		/// Realiza o acesso à variável <paramref name="varName"/>. 
		/// Se não existe, então é criada
		/// </summary>
		/// <param name="varName"></param>
		public void PushVarAccess(string varName)
		{
			ExpressionSymbol symbol;

			if ( !this.Locals.TryGetSymbol( varName, out symbol ) )
				symbol = this.Locals.Declare( varName, SymbolKind.Variable, LinqExpression.Parameter( typeof( JsObject ) ) );

			if ( symbol.Expression != null )
				this.PushStatement( symbol.Expression );
			else
				throw new InvalidOperationException( String.Format( "A variável \"{0}\" não foi declarada.", varName ) );
		}

		public override string ToString()
		{
			return this.Name ?? base.ToString();
		}
	}

}
